<?php
/**
 * Smart Library - Smart Framework
 * http://smart-framework.highet.eu
 *
 * @category   SmartL
 * @package    SmartL_Profiler
 * @copyright  Copyright (c) 2005-2008 Smart Framework team - Stepan Sindelar, Vojtech Vit
 * @license    http://www.opensource.org/licenses/bsd-license.php	New BSD License
 */

/**
 * 
 */
require_once 'Zend/Controller/Front.php';

/**
 * Class for easy profiling.
 *
 * It can generate xml or html output,
 * measure user defined timers,
 * measure memory usage in critical parts of an application,
 * profile connections of db libraries which have it's SL adapter.
 *
 * Smart Library component Profiler also inculed plugin
 * that measure memory and time of all fazes of an application.
 * This can be enabled via SmartL_Profiler::enable().
 *
 * Profiler should be enabled by hand in bootstrap as a first thing.
 * Profiler cannot be enabled in configuration.
 *
 * @TODO Oddělit profiler od Doctrine
 * @package SmartL_Profiler
 */
class SmartL_Profiler {
	/**
	 * Const for internal use.
	 * Index in array of timers.
	 *
	 */
	const START_TIME = 'startTime';
	/**
	 * Const for internal use.
	 * Index in array of timers.
	 *
	 */
	const STOP_TIME = 'stopTime';
	/**
	 * Const for internal use.
	 * Index in array of timers.
	 *
	 */
	const DEFAULT_TIMER = 'default';
	
	/**
	 * Instance of this class. Used by Singleton pattern.
	 *
	 * @var SmartL_Profiler
	 */
	private static $_instance;
	/**
	 * Information about timers.
	 * Properties are: startTime, endTime.
	 *
	 * @var array[timerName]=>array[propertyName]=>integer
	 */
	private $_timers;
	/**
	 * Array of user's custom profilers.
	 *
	 * @var SmartL_Profiler_Custom_Interface
	 */
	private $_customProfilers = array ();
	/**
	 * Information about state of memory.
	 *
	 * @var array[string name]=>float memory in MB
	 */
	private $_memoryChecks;
	/**
	 * Info about what was loaded from cache.
	 *
	 * @var array[numeric]=>string
	 */
	private $_cacheInfo = array ();
	/**
	 * Whether the html result should be appended to response.
	 *
	 * @var boolean.
	 */
	private $_appendResult = false;
	
	/**
	 * Class constructor is protected, because it implements the
	 * Singleton design pattern. Use getInstance() method instead.
	 *
	 */
	protected function __construct() {
	
	}
	/**
	 * Singleton constructor. Returns the only instance of this class.
	 *
	 * @return SmartL_Profiler
	 */
	public static function getInstance() {
		if (! self::$_instance instanceof self) {
			self::$_instance = new self ( );
		}
		return self::$_instance;
	}
	
	/**
	 * Registers profiler plugin and starts main timer.
	 *
	 * @param boolean $appendResultToResponse Default if false. If true,
	 * 					the html result will be appended to response. For this the $profileStages must be set to true.
	 * @return SmartL_Profiler
	 */
	public function enable($appendResultToResponse = false) {
		$this->start ();
		$this->_appendResult = $appendResultToResponse;
		require_once 'SmartL/Profiler/Plugin.php';
		Zend_Controller_Front::getInstance ()->registerPlugin ( new SmartL_Profiler_Plugin ( ) );
		return $this;
	}
	/**
	 * @return boolean
	 */
	public function isEnabled() {
		return count ( $this->_timers ) > 0;
	}
	/**
	 * Whether the html result should be appended to response.
	 *
	 * @return boolean
	 */
	public function getAppendResultToResponseFlag() {
		return $this->_appendResult;
	}
	/**
	 * Whehter the profiler plugin is enabled.
	 *
	 * @return boolean
	 */
	public function getProfileStagesFlag() {
		return $this->_profileStages;
	}
	
	/**
	 * Starts timer with given name.
	 *
	 * @param string $timerName Timer name
	 * @return SmartL_Profiler
	 */
	public function start($timerName = self::DEFAULT_TIMER) {
		if (! isset ( $this->_timers [$timerName] [self::START_TIME] )) {
			$this->_timers [$timerName] [self::START_TIME] = $this->_getMicrotimeFloat ();
		}
		return $this;
	}
	/**
	 * Stops timer with given name.
	 *
	 * @param string $timerName Timer name
	 * @return SmartL_Profiler
	 */
	public function stop($timerName = self::DEFAULT_TIMER) {
		if (! isset ( $this->_timers [$timerName] [self::START_TIME] )) {
			require_once 'SmartL/Profiler/Exception.php';
			throw new SmartL_Profiler_Exception ( "Cannot stop timer, before its stated." );
		}
		$this->_timers [$timerName] [self::STOP_TIME] = $this->_getMicrotimeFloat ();
		return $this;
	}
	/**
	 * Saves size of allocated memory.
	 * Memory is changing trough run of application and sometimes is good to check size of it in
	 * some important point.
	 *
	 * @param string $name You identifier for this memory check.
	 * @return SmartL_Profiler
	 */
	public function checkMemory($name) {
		$this->_memoryChecks [$name] = memory_get_usage () / (1024 * 1024);
		return $this;
	}
	/**
	 * Adds information that something was loaded from cache.
	 *
	 * @param string $name Some name or description of what was cached.
	 */
	public function addCacheInfo($name) {
		$this->_cacheInfo [] = $name;
	}
	/**
	 * Adds custom profiler.
	 *
	 * @param string $name
	 * @param SmartL_Profiler_Custom_Interface $custom
	 */
	public function addCustomProfiler($name, SmartL_Profiler_Custom_Interface $custom) {
		$this->_customProfilers [$name] = $custom;
	}
	/**
	 * Returns custom profiler with given name.
	 * If profiler with given name does not exist, returns null.
	 *
	 * @param string $name
	 * @return SmartL_Profiler_Custom_Interface
	 */
	public function getCustomProfiler($name) {
		if (isset ( $this->_customProfilers [$name] )) {
			return $this->_customProfilers [$name];
		} else {
			return null;
		}
	}
	/**
	 * Saves result into a file.
	 *
	 * @param string $filename File name
	 * @param boolean $append True if you want to append file
	 */
	public function saveResult($filename, $append = false) {
		$mode = $append ? 'a' : 'w';
		$file = fopen ( $filename, $mode );
		
		fwrite ( $filename, '<?xml version="1.0" encoding="UTF-8"?>' );
		fwrite ( $filename, "\n<html xmlns=\"http://www.w3.org/1999/xhtml\" >\n" );
		fwrite ( $filename, "\n<head><title>Profiling</title></head><body>\n" );
		fwrite ( $filename, $this->renderResult () );
		fwrite ( $filename, "\n</body></html>\n" );
	}
	
	/**
	 * Renders result of profiling in html.
	 *
	 * @param array $customProfilers If empty array(default), all custom profiler's results will be rendered, 
	 * 	oherwise indert names of custom ptofilers to be rendered.
	 * @param boolean $timers If true(default), timers times will be rendered.
	 * @param boolean $memory If true(default), memory checks will be rendered.
	 * @param boolean $cache If true(default), cache information will be rendered.
	 * @return string
	 */
	public function renderResult($customProfilers = array(), $timers = true, $memory = true, $cache = true) {
		$result = "<div id=\"profiler\">";
		$result .= "<h3>Profiling</h3>";
		
		if ($cache and count ( $this->_cacheInfo ) > 0) {
			$result .= "<div id=\"cacheInfo\"><h4>Loaded from cache</h4><table>";
			foreach ( $this->_cacheInfo as $name ) {
				$result .= "<tr><td align=\"left\">{$name}</td></tr>";
			}
			$result .= "</table></div>";
		}
		
		if ($timers) {
			$result .= "<h4>Timers</h4>";
			$result .= "<table><tr><th>name</th><th>time[s]</th></td>";
			foreach ( $this->_timers as $name => $data ) {
				$total = number_format ( $data [self::STOP_TIME] - $data [self::START_TIME], 4 );
				$result .= "<tr> <td>{$name}</td> <td> {$total} </td></tr>";
			}
			$result .= "</table>";
		}
		
		if ($memory) {
			$result .= "<div id=\"memory\"><h4>Memory</h4>";
			$result .= "<table><tr><th>stage name</th><th>memory[MB]</th></td>";
			foreach ( $this->_memoryChecks as $name => $value ) {
				$value = number_format ( $value, 4 );
				$result .= "<tr><td>{$name}</td><td>{$value}</td></tr>";
			}
			$result .= "</table></div>";
		}
		
		$customProfilers = array_keys ( $customProfilers );
		foreach ( $this->_customProfilers as $name => $profiler ) {
			if (count ( $customProfilers ) != 0 and ! isset ( $customProfilers [$name] ))
				continue;
			$result .= "<div id=\"{$name}\"><h4>{$profiler->getName()}</h4>";
			$result .= $profiler->getResult ();
			$result .= '</div>';
		}
		
		return $result;
	}
	
	/**
	 * Returns time in seconds.
	 * Helper method.
	 *
	 * @return float
	 */
	private function _getMicrotimeFloat() {
		list ( $usec, $sec ) = explode ( " ", microtime () );
		return (( float ) $usec + ( float ) $sec);
	}
}
